﻿#region Usings

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Linq;

#endregion

namespace Yandex.Market.Api.YmlSerialization
{
    /// <summary>
    ///   An XML serialization class which lets developers design the XML file structure and select the exception handling policy.
    ///   This class also supports serializing most of the collection classes.
    /// </summary>
    internal class YmlSerializer
    {
        #region Fields

        /// <summary>
        ///   The exception error behaviour enumeration to be used by the Yml library.
        /// </summary>
        private readonly YmlExceptionTypes _defaultExceptionType = YmlExceptionTypes.Warning;

        /// <summary>
        ///   The handling policy enumeration to be used by the Yml library.
        /// </summary>
        private readonly YmlExceptionHandlingPolicies _exceptionPolicy = YmlExceptionHandlingPolicies.ThrowErrorsOnly;

        /// <summary>
        ///   The list of all errors that have occured.
        /// </summary>
        private readonly YmlParsingErrors _parsingErrors = new YmlParsingErrors();

        /// <summary>
        ///   The serialization option enumeration which can be set during initialization.
        /// </summary>
        private readonly YmlSerializationOptions _serializationOption = YmlSerializationOptions.SerializeNullObjects;

        /// <summary>
        ///   The class or structure that is to be serialized/deserialized.
        /// </summary>
        private readonly Type _type;

        /// <summary>
        ///   The type wrapper for the underlying type used in the serializer
        /// </summary>
        private readonly UdtWrapper _udtWrapper;

        /// <summary>
        ///   a reference to the base xml element used during serialization.
        /// </summary>
        private XElement _baseElement;

        /// <summary>
        ///   XML document object which will hold the resulting serialization
        /// </summary>
        private XDocument _mainDocument;

        /// <summary>
        ///   if <c>true</c> an xmlns:Yandex.Market.Api.YmlSerialization attribute will be added to the top level serialized element.
        /// </summary>
        private bool _needsNamespaceAddition;

        private readonly Dictionary<string, List<XAttributeWrapper>> _elementAttributes; 

        #endregion

        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlSerializer" /> class.
        /// </summary>
        /// <param name = "type">The type of the object being serialized.</param>
        public YmlSerializer(Type type)
            : this(
                type,
                YmlExceptionHandlingPolicies.ThrowWarningsAndErrors,
                YmlExceptionTypes.Error,
                YmlSerializationOptions.SerializeNullObjects)
        {}

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlSerializer" /> class.
        /// </summary>
        /// <param name = "type">The type of the object being serialized.</param>
        /// <param name = "exceptionPolicy">The exception handling policy.</param>
        public YmlSerializer(Type type, YmlExceptionHandlingPolicies exceptionPolicy)
            : this(type, exceptionPolicy, YmlExceptionTypes.Error, YmlSerializationOptions.SerializeNullObjects)
        {}

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlSerializer" /> class.
        /// </summary>
        /// <param name = "type">The type of the object being serialized.</param>
        /// <param name = "exceptionPolicy">The exception handling policy.</param>
        /// <param name = "defaultExType">The exceptions are treated as the value specified, unless otherwise specified.</param>
        public YmlSerializer(Type type, YmlExceptionHandlingPolicies exceptionPolicy, YmlExceptionTypes defaultExType)
            : this(type, exceptionPolicy, defaultExType, YmlSerializationOptions.SerializeNullObjects)
        {}

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlSerializer" /> class.
        /// </summary>
        /// <param name = "t">The type of the object being serialized.</param>
        /// <param name = "exceptionPolicy">The exception handling policy.</param>
        /// <param name = "defaultExType">The exceptions are treated as the value specified, unless otherwise specified.</param>
        /// <param name = "option">The serialization option.</param>
        public YmlSerializer(Type t,
                             YmlExceptionHandlingPolicies exceptionPolicy,
                             YmlExceptionTypes defaultExType,
                             YmlSerializationOptions option)
        {
            _type = t;
            _exceptionPolicy = exceptionPolicy;
            _defaultExceptionType = defaultExType;
            _serializationOption = option;
            // this must be the last call
            _udtWrapper = TypeWrappersPool.Pool.GetTypeWrapper(_type, this);
            _elementAttributes = new Dictionary<string, List<XAttributeWrapper>>();
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the default type of the exception.
        /// </summary>
        /// <value>The default type of the exception.</value>
        public YmlExceptionTypes DefaultExceptionType
        {
            get { return _defaultExceptionType; }
        }

        /// <summary>
        ///   Gets the serialization option.
        /// </summary>
        /// <value>The serialization option.</value>
        public YmlSerializationOptions SerializationOption
        {
            get { return _serializationOption; }
        }

        /// <summary>
        ///   Gets the exception handling policy.
        /// </summary>
        /// <value>The exception handling policy.</value>
        public YmlExceptionHandlingPolicies ExceptionHandlingPolicy
        {
            get { return _exceptionPolicy; }
        }

        /// <summary>
        ///   Gets the parsing errors.
        /// </summary>
        /// <value>The parsing errors.</value>
        public YmlParsingErrors ParsingErrors
        {
            get { return _parsingErrors; }
        }

        #endregion

        #region Public Methods

        #region Serialization

        /// <summary>
        ///   Serializes the specified object and returns a string containing the XML.
        /// </summary>
        /// <param name = "obj">The object to serialize.</param>
        /// <returns>A <code>System.String</code> containing the XML</returns>
        public string Serialize(object obj)
        {
            return SerializeXDocument(obj).ToString();
        }

        /// <summary>
        ///   Serializes the specified object and returns an instance of <c>XDocument</c> containing the result.
        /// </summary>
        /// <param name = "obj">The object to serialize.</param>
        /// <returns>An instance of <c>XDocument</c> containing the resulting XML</returns>
        public XDocument SerializeToXDocument(object obj)
        {
            return SerializeXDocument(obj);
        }

        /// <summary>
        ///   Serializes the specified object into a <c>TextWriter</c> instance.
        /// </summary>
        /// <param name = "obj">The object to serialize.</param>
        /// <param name = "textWriter">The <c>TextWriter</c> instance.</param>
        public void Serialize(object obj, TextWriter textWriter)
        {
            textWriter.Write(Serialize(obj));
        }

        /// <summary>
        ///   Serializes the specified object into a <c>XmlWriter</c> instance.
        /// </summary>
        /// <param name = "obj">The object to serialize.</param>
        /// <param name = "xmlWriter">The <c>XmlWriter</c> instance.</param>
        public void Serialize(object obj, XmlWriter xmlWriter)
        {
            SerializeXDocument(obj).WriteTo(xmlWriter);
        }

        /// <summary>
        ///   Serializes the specified object to file.
        /// </summary>
        /// <param name = "obj">The object to serialize.</param>
        /// <param name = "fileName">Path to the file.</param>
        public void SerializeToFile(object obj, string fileName)
        {
            string ser = String.Format(CultureInfo.InvariantCulture,
                                       "{0}{1}{2}",
                                       "<?xml version=\"1.0\" encoding=\"utf-8\"?>",
                                       Environment.NewLine,
                                       Serialize(obj));
            File.WriteAllText(fileName, ser, Encoding.UTF8);
        }

        #endregion

        #endregion

        #region Public Static Methods

        /// <summary>
        ///   Cleans up auxiliary memory used by Yandex.Market.Api.YmlSerialization during different sessions of serialization.
        /// </summary>
        public static void CleanUpAuxiliaryMemory()
        {
            TypeWrappersPool.CleanUp();
        }

        #endregion

        #region Private Methods

        #region Serialization

        /// <summary>
        ///   Serializes the object into an <c>XDocument</c> object.
        /// </summary>
        /// <param name = "obj">The object to serialize.</param>
        /// <returns></returns>
        private XDocument SerializeXDocument(object obj)
        {
            _mainDocument = new XDocument();
            _mainDocument.Add(SerializeBase(obj));
            return _mainDocument;
        }

        /// <summary>
        /// One of the base methods that perform the whole job of serialization.
        /// </summary>
        /// <param name="obj">The object to be serialized</param>
        /// <returns>an instance of <c>XElement</c> which contains the result of 
        /// serialization of the specified object</returns>
        private XElement SerializeBase(object obj)
        {
            if(!_type.IsInstanceOfType(obj))
                throw new YmlObjectTypeMismatch(_type, obj.GetType());

            // to serialize stand-alone collection
            if(_udtWrapper.IsTreatedAsCollection)
            {
                if(_udtWrapper.IsTreatedAsCollection)
                    return MakeCollectionElement(null, _udtWrapper.Alias, obj, null, null);
                throw new Exception("This should not happen!");
            }

            if(ReflectionUtils.IsBasicType(_udtWrapper.UnderlyingType))
            {
                bool dummyAlreadyAdded;
                return MakeBaseElement(null, _udtWrapper.Alias, obj, out dummyAlreadyAdded);
            }

            return SerializeBase(obj, _udtWrapper.Alias);
        }

        /// <summary>
        ///   Sets the base XML element. This method is used when an <c>XMLSerializer</c>
        ///   instantiates another <c>XMLSerializer</c> to serialize nested objects.
        ///   Through this method the child objects have access to the already serialized elements of 
        ///   their parent.
        /// </summary>
        /// <param name = "baseElement">The base XML element.</param>
        private void SetBaseElement(XElement baseElement)
        {
            _baseElement = baseElement;
        }

        /// <summary>
        ///   The base method that performs the whole job of serialization. 
        ///   Other serialization methods call this method to have their job done.
        /// </summary>
        /// <param name = "obj">The object to be serialized</param>
        /// <param name = "className">Name of the element that contains the serialized object.</param>
        /// <returns>an instance of <c>XElement</c> which contains the result of 
        ///   serialization of the specified object</returns>
        private XElement SerializeBase(object obj, string className)
        {
            if(_baseElement == null)
                _baseElement = new XElement(className, null);
            else
            {
                XElement baseElem = new XElement(className, null);
                _baseElement.Add(baseElem);
                _baseElement = baseElem;
            }

            if(_udtWrapper.HasComment && _baseElement.Parent == null && _mainDocument != null)
            {
                foreach(string comment in _udtWrapper.Comment)
                    _mainDocument.Add(new XComment(comment));
            }

            // check if the main class/type has defined custom serializers
            if(_udtWrapper.HasCustomSerializer)
                InvokeCustomSerializerToElement(_udtWrapper.CustomSerializerType, obj, _baseElement);
            else // if it has no custom serializers
            {
                // iterate through public properties
                foreach(var member in GetFieldsToBeSerialized())
                {
                    if(!member.CanRead)
                        continue;

                    // ignore this member if it is attributed as dont serialize
                    if(member.IsAttributedAsDontSerialize)
                        continue;

                    if(member.MemberType == _type)
                        throw new YmlCannotSerializeSelfReferentialTypes(_type);

                    object elementValue = member.GetValue(obj);

                    // ignore this member if it is null and we are not about to serialize null objects
                    if(elementValue == null && _udtWrapper.IsNotAllowdNullObjectSerialization)
                        continue;

                    bool areOfSameType = true; // are element value and the member declared type the same?
                    object originalValue = member.GetOriginalValue(obj, null);

                    if(elementValue != null && member.MemberType != originalValue.GetType()
                       && !ReflectionUtils.IsNullable(member.MemberType))
                        areOfSameType = false;

                    bool hasCustomSerializer = member.HasCustomSerializer || member.MemberTypeWrapper.HasCustomSerializer;
                                               
                    // it gets true only for basic data types
                    if(member.IsSerializedAsAttribute && (areOfSameType || hasCustomSerializer))
                    {                       
                        if(!XmlUtils.AttributeExists(_baseElement, member.SerializationLocation, member.Alias))
                        {
                            
                            XAttributeWrapper attrToCreate = XmlUtils.CreateAttribute(_baseElement,
                                                                               member,
                                                                               hasCustomSerializer ? "" : elementValue);
                            if (attrToCreate.Attribute == null)
                                throw new YmlBadLocationException(member.SerializationLocation);

                            string elementXpath = _baseElement.GetAbsoluteXPath();

                            if (!_elementAttributes.ContainsKey(elementXpath))
                            {
                                List<XAttributeWrapper> attrWrappers = new List<XAttributeWrapper>();
                                _elementAttributes.Add(elementXpath, attrWrappers);
                            }

                            _elementAttributes[elementXpath].Add(attrToCreate);
                            _elementAttributes[elementXpath].Sort((attr1, attr2) => attr1.Order.CompareTo(attr2.Order));

                            _baseElement.ReplaceAttributes(_elementAttributes[elementXpath].Select(attrWrapper => attrWrapper.Attribute).ToArray());

                            if(member.HasCustomSerializer)
                                InvokeCustomSerializerToAttribute(member.CustomSerializerType,
                                                                  elementValue,
                                                                  attrToCreate.Attribute);
                            else if(member.MemberTypeWrapper.HasCustomSerializer)
                                InvokeCustomSerializerToAttribute(member.MemberTypeWrapper.CustomSerializerType,
                                                                  elementValue,
                                                                  attrToCreate.Attribute);
                        }
                        else
                            throw new YmlAttributeAlreadyExistsException(member.Alias);                                               
                    }
                    else if(member.IsSerializedAsValue && (areOfSameType || hasCustomSerializer))
                    {
                        // find the parent element from its location
                        XElement parElem = XmlUtils.FindLocation(_baseElement, member.SerializationLocation);
                        if(parElem == null) // if the parent element does not exist
                        {
                            // see if the location can be created
                            if(!XmlUtils.CanCreateLocation(_baseElement, member.SerializationLocation))
                                throw new YmlBadLocationException(member.SerializationLocation);
                            // try to create the location
                            parElem = XmlUtils.CreateLocation(_baseElement, member.SerializationLocation);
                            if(parElem == null)
                                throw new YmlBadLocationException(member.SerializationLocation);
                        }

                        // if control is moved here, it means that the parent 
                        // element has been found/created successfully

                        string valueToSet;
                        if(member.HasCustomSerializer)
                            valueToSet = InvokeCustomSerializerToValue(member.CustomSerializerType, elementValue);
                        else if(member.MemberTypeWrapper.HasCustomSerializer)
                            valueToSet = InvokeCustomSerializerToValue(member.MemberTypeWrapper.CustomSerializerType,
                                                                       elementValue);
                        else
                            valueToSet = (elementValue ?? String.Empty).ToString();

                        parElem.Add(new XText(valueToSet));
                    }
                    else // if the data is going to be serialized as an element
                    {
                        // find the parent element from its location
                        XElement parElem = XmlUtils.FindLocation(_baseElement, member.SerializationLocation);
                        if(parElem == null) // if the parent element does not exist
                        {
                            // see if the location can be created
                            if(!XmlUtils.CanCreateLocation(_baseElement, member.SerializationLocation))
                                throw new YmlBadLocationException(member.SerializationLocation);
                            // try to create the location
                            parElem = XmlUtils.CreateLocation(_baseElement, member.SerializationLocation);
                            if(parElem == null)
                                throw new YmlBadLocationException(member.SerializationLocation);
                        }

                        // if control is moved here, it means that the parent 
                        // element has been found/created successfully

                        if(member.HasComment)
                        {
                            foreach(string comment in member.Comment)
                                parElem.Add(new XComment(comment));
                        }

                        if(hasCustomSerializer)
                        {
                            XElement elemToFill = new XElement(member.Alias);
                            parElem.Add(elemToFill);
                            if(member.HasCustomSerializer)
                                InvokeCustomSerializerToElement(member.CustomSerializerType, elementValue, elemToFill);
                            else if(member.MemberTypeWrapper.HasCustomSerializer)
                                InvokeCustomSerializerToElement(member.MemberTypeWrapper.CustomSerializerType,
                                                                elementValue,
                                                                elemToFill);
                        }
                        else
                        {
                            // make an element with the provided data
                            bool moveDescOnly;
                            bool alreadyAdded;
                            XElement elemToAdd = MakeElement(parElem,
                                                             member,
                                                             elementValue,
                                                             out moveDescOnly,
                                                             out alreadyAdded);

                            if(!alreadyAdded)
                            {
                                if(moveDescOnly)
                                    // if only the descendants of the resulting element are going to be added ...
                                    XmlUtils.MoveDescendants(elemToAdd, parElem);
                                else
                                {
                                    // see if such element already exists
                                    XElement existingElem = parElem.Element(member.Alias);
                                    if(existingElem == null)
                                    {
                                        // if not add the new element gracefully
                                        parElem.Add(elemToAdd);
                                    }
                                    else // if an element with our desired name already exists
                                    {
                                        if(ReflectionUtils.IsBasicType(member.MemberType))
                                        {
                                            if(elementValue != null)
                                                existingElem.SetValue(elementValue);
                                        }
                                        else
                                            XmlUtils.MoveDescendants(elemToAdd, existingElem);
                                    }
                                }
                            }
                        }
                    }
                } // end of foreach var member
            } // end of else if it has no custom serializers

            if(_baseElement.Parent != null && XmlUtils.IsElementCompletelyEmpty(_baseElement))
                _baseElement.Remove();

            return _baseElement;
        }

        /// <summary>
        ///   Makes the element corresponding to the member specified.
        /// </summary>
        /// <param name = "insertionLocation">The insertion location.</param>
        /// <param name = "member">The member to serialize.</param>
        /// <param name = "elementValue">The element value.</param>
        /// <param name = "moveDescOnly">if set to <c>true</c> specifies that only the descendants of the resulting element should be added to the parent.</param>
        /// <param name = "alreadyAdded">if set to <c>true</c> specifies the element returned is 
        ///   already added to the parent element and should not be added once more.</param>
        /// <returns></returns>
        private XElement MakeElement(XElement insertionLocation,
                                     MemberWrapper member,
                                     object elementValue,
                                     out bool moveDescOnly,
                                     out bool alreadyAdded)
        {
            alreadyAdded = false;
            moveDescOnly = false;

            XElement elemToAdd;
            if(member.IsTreatedAsCollection)
            {
                elemToAdd = MakeCollectionElement(insertionLocation,
                                                  member.Alias,
                                                  elementValue,
                                                  member.CollectionAttributeInstance,
                                                  member.Format);

                if(member.CollectionAttributeInstance != null
                   &&
                   member.CollectionAttributeInstance.SerializationType
                   == YmlCollectionSerializationTypes.RecursiveWithNoContainingElement && !elemToAdd.HasAttributes)
                    moveDescOnly = true;
            }
            else
                elemToAdd = MakeBaseElement(insertionLocation, member.Alias, elementValue, out alreadyAdded);

            return elemToAdd;
        }

        /// <summary>
        ///   Adds an element contatining data related to the specified object, to an existing xml element.
        /// </summary>
        /// <param name = "elem">The parent element.</param>
        /// <param name = "alias">The name for the element to be added.</param>
        /// <param name = "obj">The object corresponding to which an element is going to be added to
        ///   an existing parent element.</param>
        /// <returns>the enclosing XML element.</returns>
        private void AddObjectToElement(XElement elem, string alias, object obj)
        {
            UdtWrapper udt = TypeWrappersPool.Pool.GetTypeWrapper(obj.GetType(), this);
            XElement elemToAdd;

            if(alias == null)
                alias = udt.Alias;

            if(udt.IsTreatedAsCollection)
            {
                elemToAdd = MakeCollectionElement(elem, alias, obj, null, null);
                elem.Add(elemToAdd);
            }
            else if(udt.IsEnum)
            {
                bool alreadyAdded;
                elemToAdd = MakeBaseElement(elem, alias, udt.EnumWrapper.GetAlias(obj), out alreadyAdded);
                if(!alreadyAdded)
                    elem.Add(elemToAdd);
            }
            else
            {
                bool alreadyAdded;
                elemToAdd = MakeBaseElement(elem, alias, obj, out alreadyAdded);
                if(!alreadyAdded)
                    elem.Add(elemToAdd);
            }
        }

        /// <summary>
        ///   Serializes a collection object.
        /// </summary>
        /// <param name = "insertionLocation">The insertion location.</param>
        /// <param name = "elementName">Name of the element.</param>
        /// <param name = "elementValue">The object to be serailized.</param>
        /// <param name = "collectionAttrInst">The collection attribute instance.</param>
        /// <param name = "format">formatting string, which is going to be applied to all members of the collection.</param>
        /// <returns>
        ///   an instance of <c>XElement</c> which will contain the serailized collection
        /// </returns>
        private XElement MakeCollectionElement(XElement insertionLocation,
                                               string elementName,
                                               object elementValue,
                                               YmlCollectionAttribute collectionAttrInst,
                                               string format)
        {
            if(elementValue == null)
                return new XElement(elementName, null);

            if(!(elementValue is IEnumerable))
                throw new ArgumentException("elementValue must be an IEnumerable");

            IEnumerable collectionInst = elementValue as IEnumerable;
            YmlCollectionSerializationTypes serType = YmlCollectionSerializationTypes.Recursive;
            string seperator = string.Empty;
            string eachElementName = null;

            if(collectionAttrInst != null)
            {
                serType = collectionAttrInst.SerializationType;
                seperator = collectionAttrInst.SeparateBy;
                eachElementName = collectionAttrInst.EachElementName;
            }

            Type colItemType = ReflectionUtils.GetCollectionItemType(elementValue.GetType());
            /*
            if (eachElementName == null)
            {
                eachElementName = ReflectionUtils.GetTypeFriendlyName(colItemType);
            }
            */
            if(serType == YmlCollectionSerializationTypes.Serially && !ReflectionUtils.IsBasicType(colItemType))
                serType = YmlCollectionSerializationTypes.Recursive;

            UdtWrapper colItemsUdt = TypeWrappersPool.Pool.GetTypeWrapper(colItemType, this);

            XElement elemToAdd; // will hold the resulting element
            if(serType == YmlCollectionSerializationTypes.Serially)
            {
                StringBuilder sb = new StringBuilder();

                bool isFirst = true;
                foreach(object obj in collectionInst)
                {
                    object objToAdd;
                    if(colItemsUdt.IsEnum)
                        objToAdd = colItemsUdt.EnumWrapper.GetAlias(obj);
                    else if(format != null)
                        objToAdd = ReflectionUtils.TryFormatObject(obj, format);
                    else
                        objToAdd = obj;

                    if(isFirst)
                    {
                        sb.Append(objToAdd.ToString());
                        isFirst = false;
                    }
                    else
                        sb.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", seperator, objToAdd);
                }

                bool alreadyAdded;
                elemToAdd = MakeBaseElement(insertionLocation, elementName, sb.ToString(), out alreadyAdded);
                if(alreadyAdded)
                    elemToAdd = null;
            }
            else
            {
                XElement elem = new XElement(elementName, null);
                foreach(var obj in collectionInst)
                {
                    var objToAdd = (format == null) ? obj : ReflectionUtils.TryFormatObject(obj, format);
                    var curElemName = eachElementName;

                    if(curElemName == null)
                    {
                        UdtWrapper udt = TypeWrappersPool.Pool.GetTypeWrapper(obj.GetType(), this);
                        curElemName = udt.Alias;
                    }

                    AddObjectToElement(elem, curElemName, objToAdd);
                }

                elemToAdd = elem;
            }

            return elemToAdd;
        }

        /// <summary>
        ///   Makes an XML element with the specified name, corresponding to the object specified.
        /// </summary>
        /// <param name = "insertionLocation">The insertion location.</param>
        /// <param name = "name">The name of the element.</param>
        /// <param name = "value">The object to be serialized in an XML element.</param>
        /// <param name = "alreadyAdded">if set to <c>true</c> specifies the element returned is 
        ///   already added to the parent element and should not be added once more.</param>
        /// <returns>
        ///   an instance of <c>XElement</c> which will contain the serialized object,
        ///   or <c>null</c> if the serialized object is already added to the base element
        /// </returns>
        private XElement MakeBaseElement(XElement insertionLocation, string name, object value, out bool alreadyAdded)
        {
            alreadyAdded = false;
            if(value == null || ReflectionUtils.IsBasicType(value.GetType()))
            {
                if(value != null)
                {
                    value = value is bool
                                ? Convert.ToString(value, CultureInfo.InvariantCulture).ToLower()
                                : Convert.ToString(value, CultureInfo.InvariantCulture);
                }
                    

                return new XElement(name, value);
            }
            if(ReflectionUtils.IsStringConvertibleIFormattable(value.GetType()))
            {
                var elementValue = value.GetType().InvokeMember("ToString",
                                                                   BindingFlags.InvokeMethod,
                                                                   null,
                                                                   value,
                                                                   new object[0]);
                return new XElement(name, elementValue);
            }
            YmlSerializer ser = new YmlSerializer(value.GetType(),
                                                  _exceptionPolicy,
                                                  _defaultExceptionType,
                                                  _serializationOption);
            ser.SetBaseElement(insertionLocation);
            XElement elem = ser.SerializeBase(value, name);

            if(ser._needsNamespaceAddition)
                _needsNamespaceAddition = true;

            _parsingErrors.AddRange(ser.ParsingErrors);
            alreadyAdded = true;
            return elem;
        }

        #endregion

        #region Utilities

        private static void InvokeCustomSerializerToElement(Type customSerType,
                                                            object objToSerialize,
                                                            XElement elemToFill)
        {
            object customSerializer = customSerType.InvokeMember(string.Empty,
                                                                 BindingFlags.CreateInstance,
                                                                 null,
                                                                 null,
                                                                 new object[0]);
            customSerType.InvokeMember("SerializeToElement",
                                       BindingFlags.InvokeMethod,
                                       null,
                                       customSerializer,
                                       new[] {objToSerialize, elemToFill});
        }

        private static void InvokeCustomSerializerToAttribute(Type customSerType,
                                                              object objToSerialize,
                                                              XAttribute attrToFill)
        {
            object customSerializer = customSerType.InvokeMember(string.Empty,
                                                                 BindingFlags.CreateInstance,
                                                                 null,
                                                                 null,
                                                                 new object[0]);
            customSerType.InvokeMember("SerializeToAttribute",
                                       BindingFlags.InvokeMethod,
                                       null,
                                       customSerializer,
                                       new[] {objToSerialize, attrToFill});
        }

        private static string InvokeCustomSerializerToValue(Type customSerType, object objToSerialize)
        {
            object customSerializer = customSerType.InvokeMember(string.Empty,
                                                                 BindingFlags.CreateInstance,
                                                                 null,
                                                                 null,
                                                                 new object[0]);
            return
                (string)
                customSerType.InvokeMember("SerializeToValue",
                                           BindingFlags.InvokeMethod,
                                           null,
                                           customSerializer,
                                           new[] {objToSerialize});
        }

        /// <summary>
        ///   Gets the sequence of fields to be serialized for the specified type. This sequence is retreived according to 
        ///   the field-types specified by the user.
        /// </summary>
        /// <param name = "typeWrapper">The type wrapper for the type whose serializable 
        ///   fields is going to be retreived.</param>
        /// <returns>the sequence of fields to be serialized for the specified type</returns>
        private IEnumerable<MemberWrapper> GetFieldsToBeSerialized(UdtWrapper typeWrapper)
        {
            return
                from member in
                    typeWrapper.UnderlyingType.GetMembers(BindingFlags.Instance | BindingFlags.NonPublic
                                                          | BindingFlags.Public)
                let name0 = member.Name[0]
                where
                    (Char.IsLetter(name0) || name0 == '_')
                    && (member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Field)
                select new MemberWrapper(member, this)
                into memInfo where memInfo.IsAllowedToBeSerialized(typeWrapper.FieldsToSerialize) select memInfo;
        }

        /// <summary>
        ///   Gets the sequence of fields to be serialized for the serializer's underlying type. 
        ///   This sequence is retreived according to the field-types specified by the user.
        /// </summary>
        /// <returns>the sequence of fields to be serialized for the serializer's underlying type.</returns>
        private IEnumerable<MemberWrapper> GetFieldsToBeSerialized()
        {
            List<MemberWrapper> memberWrappers = new List<MemberWrapper>(GetFieldsToBeSerialized(_udtWrapper));
            memberWrappers.Sort((memberWrapper1, memberWrapper2) => memberWrapper1.Order.CompareTo(memberWrapper2.Order));
            return memberWrappers; //GetFieldsToBeSerialized(_udtWrapper);
        }

        #endregion

        // Private methods

        #endregion
    }
}